/*
*  Datagram Socket Implementation
*/

#include "DatagramSocket.h"
#include <string.h>

//---------------------------------------------------------------------------

#ifdef __MAGUS_WIN32
    typedef unsigned long in_addr_t;
    typedef int socklen_t;
    #define op_errno WSAGetLastError()
    #define OP_EWOULDBLOCK WSAEWOULDBLOCK
#else
    #define INVALID_SOCKET -1
    #define SOCKET_ERROR -1
    #ifndef INADDR_NONE
    #define INADDR_NONE -1
    #endif
#if defined(sun) || defined(__sun)
    #ifndef _IN_ADDR_T
    typedef unsigned long in_addr_t;
    #endif
    #ifndef _SOCKLEN_T
    typedef int socklen_t;
    #endif    
#endif
    #define op_errno errno 
    #define OP_EWOULDBLOCK EWOULDBLOCK
#endif

//---------------------------------------------------------------------------

namespace 
{
    const int GETHOSTBUFLEN = 512;
}

//---------------------------------------------------------------------------
DatagramSocket::DatagramSocket() 
    : _M_fd(INVALID_SOCKET)
{
    memset(_M_hostname, '\0', HOSTNAME_LEN);
#ifdef __MAGUS_WIN32
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2), &wsaData);
#endif
    _M_fd = socket(AF_INET, SOCK_DGRAM, 0);
}

//---------------------------------------------------------------------------
void DatagramSocket::close()
{
    if (_M_fd != INVALID_SOCKET) 
    {
#ifdef __MAGUS_WIN32
        closesocket(_M_fd);
#else
        ::close(_M_fd);
#endif
        _M_fd = INVALID_SOCKET;
    }
}

//---------------------------------------------------------------------------
DatagramSocket::~DatagramSocket()
{
    this->close();
#ifdef __MAGUS_WIN32
    WSACleanup();
#endif
}

//---------------------------------------------------------------------------
int DatagramSocket::connect(const char* host, int port)
{
    if (_M_fd == INVALID_SOCKET)
        return -1;
    int herror = getInetAddressOf(host, &_M_dest);
    if (herror != 0)
        return herror;
    _M_port = port;
    _M_dest.sin_family = AF_INET;
    _M_dest.sin_port = htons((unsigned short)_M_port);
    return 0;
}


int DatagramSocket::send(const char *buf, int len)
{
    return send((const struct sockaddr*)&_M_dest, buf, len);
}


//---------------------------------------------------------------------------
int DatagramSocket::send(const struct sockaddr *sa, const char *buf, int len)
{
    int rv;

#ifdef __MAGUS_WIN32
    rv = sendto(_M_fd, buf, len, 0, sa, sizeof(sockaddr_in));
#else
    do {
        rv = sendto(_M_fd, buf, len, 0, sa, sizeof(sockaddr_in));
    } while (rv == SOCKET_ERROR && errno == EINTR);
#endif

    if (rv == SOCKET_ERROR && (op_errno == OP_EWOULDBLOCK))
    {
        fd_set fdwrite;
        timeval tv;
        tv.tv_sec = _M_timeout;
        tv.tv_usec = 0; 
        FD_ZERO(&fdwrite);
        FD_SET(_M_fd, &fdwrite);
#ifdef __MAGUS_WIN32
        rv = select(_M_fd+1, NULL, &fdwrite, NULL, &tv);
#else
        do {
            rv = select(_M_fd+1, NULL, &fdwrite, NULL, &tv);
        }while (rv < 0 && errno == EINTR);
#endif
        if (rv <= 0) {
            return rv;
        }
#ifdef __MAGUS_WIN32
        rv = sendto(_M_fd, buf, len, 0, sa, sizeof(sockaddr_in));
#else
        do {
            rv = sendto(_M_fd, buf, len, 0, sa, sizeof(sockaddr_in));
        } while (rv == SOCKET_ERROR && errno == EINTR);
#endif  
    }
    return rv;
}

//---------------------------------------------------------------------------

int DatagramSocket::listen(int port)
{
    if (_M_fd == INVALID_SOCKET)
        return -1;

    // inet address
    _M_port = port;
    _M_dest.sin_family = AF_INET;
    _M_dest.sin_port = htons((unsigned short)_M_port);
    _M_dest.sin_addr.s_addr = INADDR_ANY;
    if (bind(_M_fd, (sockaddr *)&_M_dest, sizeof(_M_dest)) == SOCKET_ERROR)
    {
        return -1;
    }
    return 0;
}

//---------------------------------------------------------------------------
int DatagramSocket::recv(char *buf, int len)
{
    int rv;
    int salen = sizeof(sockaddr);

#ifdef __MAGUS_WIN32
    rv = recvfrom(_M_fd, buf, len, 0, 
        (struct sockaddr*)&_M_dest, (socklen_t *)&salen);
#else
    do {
        rv = recvfrom(_M_fd, buf, len, 0, 
            (struct sockaddr*)&_M_dest, (socklen_t *)&salen);
    } while (rv == SOCKET_ERROR && errno == EINTR);
#endif
    
    if (rv == SOCKET_ERROR && (op_errno == OP_EWOULDBLOCK))
    {
        fd_set fdread;
        timeval tv;
        tv.tv_sec = _M_timeout;
        tv.tv_usec = 0;
        FD_ZERO(&fdread);
        FD_SET(_M_fd, &fdread);
#ifdef __MAGUS_WIN32
        rv = select(_M_fd+1, &fdread, NULL, NULL, &tv);
#else
        do{
            rv = select(_M_fd+1, &fdread, NULL, NULL, &tv);
        }while (rv < 0 && errno == EINTR);
#endif
        if (rv <= 0) {
            return rv;
        }
#ifdef __MAGUS_WIN32
        rv = recvfrom(_M_fd, buf, len, 0, 
            (struct sockaddr*)&_M_dest, (socklen_t *)&salen);
#else
        do {
            rv = recvfrom(_M_fd, buf, len, 0, 
                (struct sockaddr*)&_M_dest, (socklen_t *)&salen);
        } while (rv == SOCKET_ERROR && errno == EINTR);
#endif  
    }
    return rv;
}

char *DatagramSocket::getHostName()
{
    if (_M_fd == INVALID_SOCKET) 
    {
        _M_hostname[0] = '\0';
        return _M_hostname;
    }
    struct hostent* hp;
#ifdef __MAGUS_WIN32
    hp = gethostbyaddr((char *)&_M_dest.sin_addr, sizeof(struct in_addr), AF_INET);
#else
    struct hostent hs;
    char tmp[GETHOSTBUFLEN];
    int herror;
#if defined(linux) || defined(__linux) || defined(__linux__)
    gethostbyaddr_r((char *)&_M_dest.sin_addr, sizeof(struct in_addr), AF_INET,
        &hs, tmp, GETHOSTBUFLEN-1, &hp, &herror);
#else
    hp = gethostbyaddr_r((char *)&_M_dest.sin_addr, sizeof(struct in_addr), AF_INET,
        &hs, tmp, GETHOSTBUFLEN, &herror);
#endif    /* !__LINUX */
#endif  /* !__WIN32 */
    
    if (hp == NULL)
        return getHostAddress();

    strncpy(_M_hostname, hp->h_name, HOSTNAME_LEN-1); 
    return _M_hostname;
}

char *DatagramSocket::getHostAddress()
{
    if (_M_fd == INVALID_SOCKET) 
    {
        _M_hostname[0] = '\0';
        return _M_hostname;
    }

#ifdef __MAGUS_WIN32
    char *ipaddr = inet_ntoa(_M_dest.sin_addr);
    strncpy(_M_hostname, ipaddr, HOSTNAME_LEN-1); ;
#else
    //char buf[INET_ADDRSTRLEN];      /* space for 255.255.255.255\0 */
    inet_ntop(AF_INET, &_M_dest.sin_addr, _M_hostname, INET_ADDRSTRLEN);
#endif
    return _M_hostname;
}

int DatagramSocket::getMyAddress()
{
    struct sockaddr_in sa;
    char host[256];
    
    gethostname(host, 256);
    int rv = DatagramSocket::getInetAddressOf(host, &sa);
    if (rv == 0)
        return sa.sin_addr.s_addr;
    return 0;
}

int DatagramSocket::getInetAddressOf(const char* host, sockaddr_in *sa)
{
    in_addr_t addr;
    hostent* hp = NULL;
    int herror = -1;
    if ((addr = inet_addr(host)) != (in_addr_t)INADDR_NONE) 
    {  
        memcpy(&sa->sin_addr.s_addr, &addr, sizeof(addr));    
    }else {
#ifdef __MAGUS_WIN32
        hp = gethostbyname(host);
#else
        hostent hs;
        char tmp[GETHOSTBUFLEN];
#if defined(linux) || defined(__linux) || defined(__linux__)
        gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &hp, &herror);
#else
        hp = gethostbyname_r(host, &hs, tmp, GETHOSTBUFLEN-1, &herror);
#endif
#endif
        if (hp == NULL)      
            return herror;                   
        memcpy(&sa->sin_addr.s_addr, hp->h_addr_list[0], sizeof(in_addr_t));
    }
    return 0;
}

//---------------------------------------------------------------------------
int DatagramSocket::setTimeout(int timeout)
{
#ifdef __MAGUS_WIN32
    u_long noblock;
    if (timeout <= 0) 
        noblock = 0;
    else    
        noblock = 1;
    if (ioctlsocket(_M_fd, FIONBIO, &noblock) == SOCKET_ERROR)
        return -1;
    _M_timeout = timeout;
    return 0;
#else
    int fd_flags;
    if ((fd_flags = fcntl(_M_fd, F_GETFL, 0)) == -1 )
        return -1;
    if (timeout <= 0) 
        fd_flags &= ~O_NONBLOCK;
    else    
        fd_flags |= O_NONBLOCK;
    if (fcntl(_M_fd, F_SETFL, fd_flags) == -1)
        return -1;
    _M_timeout = timeout;
    return 0;
#endif
}

//---------------------------------------------------------------------------
